package cn.jofei.client.netty.handler;

import cn.jofei.client.abstra.Client;
import cn.jofei.client.core.NettyClientStartFunction;
import cn.jofei.client.netty.SocketClient;
import cn.jofei.client.netty.SocketServerConnector;
import cn.jofei.common.ProxyInfo;
import cn.jofei.common.config.AppProperties;
import cn.jofei.common.enums.Types;
import cn.jofei.common.netty.handler.RelayHandler;
import cn.jofei.common.netty.message.Message;
import cn.jofei.common.netty.protocol.MessageCodec;
import cn.jofei.common.netty.protocol.ProtocolFrameDecoder;
import cn.jofei.common.utils.Utils;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static cn.jofei.common.netty.message.MsgType.*;
import static cn.jofei.common.netty.message.MsgType.CONNECT_ESTABLISHED;

/**
 * 客户端信息处理
 *
 * @author Jofei Bao
 * @version ngbao v2.0
 * @date 2022年10月17日 21:01
 */
@ChannelHandler.Sharable
public class ServiceMessageHandler extends SimpleChannelInboundHandler<Message> {

    private final Logger logger = LoggerFactory.getLogger(ServiceMessageHandler.class);

    private final Map<Integer, ProxyInfo> proxyInfoMap = new ConcurrentHashMap<>();

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        NettyClientStartFunction.cancel();
        InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
        logger.info("远程连接创建成功，IP：{}，端口：{}", address.getHostName(), address.getPort());
        //注册客户端信息
        List<ProxyInfo> proxyInfos = proxyInfo();
        for (ProxyInfo proxyInfo : proxyInfos) {
            Message message = Message.of(proxyInfo);
            proxyInfoMap.put(message.getSequenceId(), proxyInfo);
            logger.info("注册信息：{}", message);
            ctx.writeAndFlush(message);
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
        logger.error("与服务器断开连接，IP：{}，端口：{}", address.getHostName(), address.getPort());
        NettyClientStartFunction.startJob();
    }

    @Override
    protected void channelRead0(ChannelHandlerContext context, Message message) throws Exception {
        try {
            if (Utils.match(message)) {
                Integer type = message.getMsgType();
                ProxyInfo proxyInfo;
                switch (type) {
                    case HTTP_NOTIFY:
                        proxyInfo = Client.REG_INFOS.get(message.getSubdomain());
                        if (proxyInfo != null) {
                            createProxy(message, proxyInfo);
                        }
                        break;
                    case TCP_NOTIFY:
                        proxyInfo = Client.REG_INFOS.get(message.getPort() + "");
                        // 创建本地新连接，创建新线程进行消息转发
                        if (proxyInfo != null) {
                            createProxy(message, proxyInfo, true);
                        }
                        break;
                    case REG_RESPONSE:
                        proxyInfo = proxyInfoMap.remove(message.getSequenceId());
                        if (message.getSuccess() && Utils.nonNull(proxyInfo)) {
                            Client.register(proxyInfo);
                        } else {
                            logger.error(message.getMsg());
                        }
                        break;
                    case CONNECT_ESTABLISHED:
                        SocketServerConnector.localChannelAutoRead(message.getSequenceId());
                        break;
                    default:
                        break;
                }
            } else {
                logger.error("密钥不匹配:{}", message.getMsg());
            }
        } finally {
            ReferenceCountUtil.safeRelease(message);
        }
    }

    private void createProxy(Message message, ProxyInfo proxyInfo) throws InterruptedException {
        createProxy(message, proxyInfo, false);
    }

    private void createProxy(Message message, ProxyInfo proxyInfo, boolean tcp) throws InterruptedException {
        message.setMsgType(tcp ? TCP_RESPOND : HTTP_RESPOND);
        /**
         * 建立本地连接
         */
        Bootstrap b = new Bootstrap();
        b.group(SocketClient.WORK_GROUP) // 和 clientChannel 使用同一个 EventLoop
                .channel(NioSocketChannel.class)
                .handler(new HttpRequestEncoder());
        ChannelFuture f = b.connect(proxyInfo.getLocalIP(), proxyInfo.getLocalPort());

        f.addListener((ChannelFutureListener) future -> {
            if (future.isSuccess()) {
                Channel local = future.channel();
                // 建立远程连接
                ChannelFuture remoteFuture = SocketServerConnector.newServerChannelFuture();
                remoteFuture.addListener((ChannelFutureListener) e -> {
                    Channel remote = e.channel();
                    remote.writeAndFlush(message.reCode());
                    remote.config().setAutoRead(false);

                    local.pipeline().remove(HttpRequestEncoder.class);
                    local.pipeline().addLast(new RelayHandler(remote));

                    remote.pipeline().remove(LoggingHandler.class);
                    remote.pipeline().remove(ProtocolFrameDecoder.class);
                    remote.pipeline().remove(IdleStateHandler.class);
                    remote.pipeline().remove(MessageCodec.class);
                    remote.pipeline().addLast(new RelayHandler(local));

                    // connection is ready, enable AutoRead
                    remote.config().setAutoRead(true);

                    SocketServerConnector.saveChannel(message.getSequenceId(), local);
                });
            } else {
                logger.error("创建代理失败", future.cause());
            }
        });

    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            switch (event.state()) {
                case ALL_IDLE:
                    logger.debug("读写都超时");
                    break;
                case READER_IDLE:
                    logger.debug("读超时");
                    break;
                case WRITER_IDLE:
                    logger.debug("发送心跳信息");
                    ctx.writeAndFlush(Message.ping());
                    break;
            }
        }
    }

    /**
     * 读取配置信息
     *
     * @return
     */
    private List<ProxyInfo> proxyInfo() {
        List<String> clients = AppProperties.get("server.client", List.class).orElse(new ArrayList());
        List<ProxyInfo> lists = null;
        if (Utils.nonNull(clients)) {
            lists = new ArrayList<>();
            for (String client : clients) {
                ProxyInfo info = new ProxyInfo();
                if (client.startsWith("tcp:")) {
                    info.setType(Types.TCP);
                    info.setRemotePort(Integer.parseInt(client.split(" ")[0].split(":")[1]));
                    if (client.split(" ")[1].contains(":")) {
                        info.setLocalIP(client.split(" ")[1].split(":")[0]);
                        info.setLocalPort(Integer.parseInt(client.split(" ")[1].split(":")[1]));
                    } else {
                        info.setLocalIP("127.0.0.1");
                        info.setLocalPort(Integer.parseInt(client.split(" ")[1]));
                    }
                } else {
                    String[] split = client.split(" ");
                    info.setType(Types.WEB);
                    info.setSubdomain(split[0]);
                    if (split[1].contains(":")) {
                        info.setLocalIP(split[1].split(":")[0]);
                        info.setLocalPort(Integer.parseInt(split[1].split(":")[1]));
                    } else {
                        if (Utils.isNum(split[1])) {
                            info.setLocalIP("127.0.0.1");
                            info.setLocalPort(Integer.parseInt(split[1]));
                        } else {
                            info.setLocalIP(split[1]);
                            info.setLocalPort(80);
                        }
                    }
                }
                lists.add(info);
            }
        }
        return lists;
    }
}
